﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace CrossGenLogic
{
    public class GridGeneration
    {
        private static int MaxCount = 30;
        private static int MinCount = 10;
        private int ElementsCount;
        private int height, width;
        private Random grid_rand;

        public GridGeneration(int i)
        {
            if (i <= MaxCount && i >= MinCount)
            {
                ElementsCount = i;
                grid_rand = new Random(DateTime.Now.Millisecond);
            }
        }

        private int CompareByY(GridElement x, GridElement y)
        {
            if (x.Y > y.Y) return 1;
            if (x.Y == y.Y) return 0;
            else return -1;
        }

        public int Width
        {
            get
            {
                return width;
            }
        }

        public int Height
        {
            get
            {
                return height;
            }
        }

        private bool isContain(List<GridElement> GE, int X, int Y)
        {
            for (int i = 0; i < GE.Count; i++)
            {
                if ((GE[i].X == X && GE[i].Y == Y) || ((GE[i].X + GE[i].dx() * (GE[i].Length - 1)) == X && (GE[i].Y + GE[i].dy() * (GE[i].Length - 1)) == Y))
                {
                    return true;
                }
            }
            return false;
        }

        private bool isImpossible(List<GridElement> GE, int X, int Y1, int Y2)
        {
            for (int i = 0; i < GE.Count; i++)
            {
                if (GE[i].Y < Y2 && GE[i].Y > Y1 && X + 1 == GE[i].X) return true;
            }
            return false;
        }

        private bool isImpossibleRight(List<GridElement> GE, int X, int Y1, int Y2)
        {
            for (int i = 0; i < GE.Count; i++)
            {
                if (GE[i].Y < Y2 && GE[i].Y > Y1 && X - 1 == (GE[i].X + GE[i].Length + GE[i].dx() - 1)) return true;
            }
            return false;
        }

        private GridElement SearchPosLeft(List<GridElement> Horizontal, List<GridElement> Vertical, int base_x, int base_length)
        {
            Horizontal.Sort(CompareByY);
            if (Horizontal.Count == 1)
            {
                for (int j = 0; j < (Horizontal[0].Length - 1) / 2; j++)
                {
                    if (!isContain(Vertical, base_x - 2 * (1 + j), Horizontal[0].Y))
                    {
                        return new GridElement(base_x - 2 * (1 + j), Horizontal[0].Y, false, grid_rand.Next(3, base_length + 1));
                    }
                }
            }
            else
            {
                for (int i = 0; i < Horizontal.Count - 1; i++)
                {
                    for (int j = 0; j < (Horizontal[i].Length - 1) / 2; j++)
                    {
                        if (!isContain(Vertical, base_x - 2 * (1 + j) - i % 2, Horizontal[i].Y))
                        {
                            if (!isContain(Vertical, base_x - 2 * (1 + j) - i % 2, Horizontal[i + 1].Y) &&
                                !isImpossible(Horizontal, base_x - 2 * (1 + j) - i % 2, Horizontal[i].Y, Horizontal[i + 1].Y))
                                return new GridElement(base_x - 2 * (1 + j) - i % 2, Horizontal[i].Y, false, Horizontal[i + 1].Y - Horizontal[i].Y + 1);
                        }
                    }
                }
            }
            return null;
        }

        private GridElement SearchPosRight(List<GridElement> Horizontal, List<GridElement> Vertical, int base_x, int base_length, int right_max)
        {
            int k;
            Horizontal.Sort(CompareByY);
            if (Horizontal.Count == 1)
            {
                for (int j = 0; j < (Horizontal[0].Length - 1) / 2; j++)
                {
                    if (!isContain(Vertical, base_x + 2 * (1 + j), 0))
                    {
                        return new GridElement(base_x + 2 * (1 + j), 0, false, Horizontal[0].Y + 1);
                    }
                }
            }
            else
            {
                k = Horizontal.Count - 1;
                if (base_x % 2 == 0 && Horizontal.Count > 2) k++;
                try
                {
                    for (int i = 0; i < k; i++)
                    {
                        for (int j = 0; j < (Horizontal[i].Length - 1) / 2; j++)
                        {
                            if (!isContain(Vertical, base_x + 2 * (1 + j) + i % 2, Horizontal[i].Y))
                            {
                                if (!isContain(Vertical, base_x + 2 * (1 + j) + i % 2, Horizontal[i + 1].Y) &&
                                    !isImpossibleRight(Horizontal, base_x + 2 * (1 + j) + i % 2, Horizontal[i].Y, Horizontal[i + 1].Y))
                                {
                                    if (i < k - 2) return new GridElement(base_x + 2 * (1 + j) + i % 2, Horizontal[i].Y, false, Horizontal[i + 1].Y - Horizontal[i].Y + 1);
                                    else
                                    {
                                        if (i == k - 2 && (2 * (1 + j) + i % 2) != 3) return new GridElement(base_x + 2 * (1 + j) + i % 2, Horizontal[i].Y, false, Horizontal[i + 1].Y - Horizontal[i].Y + 1);
                                        if (i == k - 1 && (2 * (1 + j) + i % 2) != 3) return new GridElement(base_x + 2 * (1 + j) + i % 2, Horizontal[i].Y, false, 3);
                                    }
                                }
                            }
                        }
                    }
                }
                catch (ArgumentOutOfRangeException e)
                {
                    MessageBox.Show("Произошла ошибка при построении сетки. "+e.Message);
                }
            }
            return null;
        }

        public List<GridElement> GenerateGrid()
        {
            int base_length;
            int vert_count;
            int horiz_count;
            int base_x;
            int i, j, k;
            int right_max;
            int left_max_y;
            int R_add = 0, L_add = 0;
            GridElement temp;

            List<GridElement> Left_Vertical = new List<GridElement>();
            List<GridElement> Left_Horizontal = new List<GridElement>();
            List<GridElement> Right_Vertical = new List<GridElement>();
            List<GridElement> Right_Horizontal = new List<GridElement>();
            List<GridElement> AllGrid = new List<GridElement>();
            List<int> Busy = new List<int>();
            List<int> Busy_vert = new List<int>();

            base_length = Convert.ToInt32(Math.Floor(Convert.ToDouble(ElementsCount / 2 - 6)));
            if (base_length < 6)
            {
                if (base_length < 5) base_length = 5;
                if (base_length == 5) base_length = 6;
            }
            if (base_length > 8) base_length = 8;
            base_x = grid_rand.Next(5, 8);
            AllGrid.Add(new GridElement(base_x, 0, false, base_length));
            vert_count = Convert.ToInt32(Math.Floor(Convert.ToDouble((ElementsCount / 2 - 2) / 2)));
            horiz_count = Convert.ToInt32(Math.Floor(Convert.ToDouble(ElementsCount / 2))) - 2 - vert_count;

            Busy.Add(0);
            left_max_y = 0;
            i = grid_rand.Next(5, base_x + 1);
            Left_Horizontal.Add(new GridElement(base_x - i + 1, 0, true, i));

            if (base_length % 2 == 0)
            {
                right_max = grid_rand.Next(5, base_x + 2);
                Right_Horizontal.Add(new GridElement(base_x, base_length - 3, true, right_max));
                Busy.Add(base_length - 3);
            }
            else
            {
                right_max = grid_rand.Next(5, base_x + 2);
                Right_Horizontal.Add(new GridElement(base_x, base_length - 2, true, right_max));
                Busy.Add(base_length - 2);
            }
            if (horiz_count > 2)
            {
                i = grid_rand.Next(5, base_x + 2);
                if (base_length % 2 == 0)
                {
                    Left_Horizontal.Add(new GridElement(base_x - i + 1, base_length - 2, true, i));
                    Busy.Add(base_length - 2);
                    if (base_length - 2 > left_max_y) left_max_y = base_length - 2;
                }
                else
                {
                    Left_Horizontal.Add(new GridElement(base_x - i + 1, base_length - 1, true, i));
                    Busy.Add(base_length - 1);
                    if (base_length - 1 > left_max_y) left_max_y = base_length - 1;
                }
            }
            if (horiz_count > 3)
            {
                for (i = 3; i < horiz_count; i++)
                {
                    if (i % 2 != 0)
                    {
                        while (true)
                        {
                            j = grid_rand.Next(1, base_length - 1);
                            if (j % 2 != 0 && !Busy.Contains(j))
                            {
                                k = grid_rand.Next(5, base_x + 2);
                                if (k > right_max) right_max = k;
                                Right_Horizontal.Add(new GridElement(base_x, j, true, k));
                                Busy.Add(j);
                                break;
                            }
                        }
                    }
                    else
                    {
                        while (true)
                        {
                            j = grid_rand.Next(2, base_length - 1);
                            if (j % 2 == 0 && !Busy.Contains(j))
                            {
                                k = grid_rand.Next(4, base_x + 2);
                                Left_Horizontal.Add(new GridElement(base_x - k + 1, j, true, k));
                                Busy.Add(j);
                                if (j > left_max_y) left_max_y = j;
                                break;
                            }
                        }
                    }
                }
            }
            if (base_length % 2 == 0)
            {
                Left_Vertical.Add(new GridElement(base_x - 2, left_max_y, false, 2 * base_length - left_max_y - base_length + 3));
                Busy_vert.Add(base_x - 2);
                Busy_vert.Add(base_x + 3);
            }
            else
            {
                Left_Vertical.Add(new GridElement(base_x - 2, left_max_y, false, 2 * base_length - left_max_y - base_length + 2));
                Busy_vert.Add(base_x - 2);
                Busy_vert.Add(base_x + 3);
            }

            for (i = 0; i < vert_count; i++)
            {
                if (i % 2 == 0)
                {
                    temp = SearchPosRight(Right_Horizontal, Right_Vertical, base_x, base_length, right_max);
                    if (temp == null) L_add++;
                    else Right_Vertical.Add(temp);
                }
                else
                {
                    temp = SearchPosLeft(Left_Horizontal, Left_Vertical, base_x, base_length);
                    if (temp == null) R_add++;
                    else Left_Vertical.Add(temp);
                }
            }

            if (R_add > 0 && L_add > 0) return new List<GridElement>();

            if (R_add > 0)
            {
                for (i = 0; i < R_add; i++)
                {
                    temp = SearchPosRight(Right_Horizontal, Right_Vertical, base_x, base_length, right_max);
                    if (temp == null) return new List<GridElement>();
                    else Right_Vertical.Add(temp);
                }
            }

            if (L_add > 0)
            {
                for (i = 0; i < L_add; i++)
                {
                    temp = SearchPosLeft(Left_Horizontal, Left_Vertical, base_x, base_length);
                    if (temp == null) return new List<GridElement>();
                    else Left_Vertical.Add(temp);
                }
            }

            height = base_length * 2;
            width = (base_x + 1) * 2;
            AllGrid.AddRange(Left_Vertical);
            AllGrid.AddRange(Left_Horizontal);
            AllGrid.AddRange(Right_Horizontal);
            AllGrid.AddRange(Right_Vertical);

            j = AllGrid.Count;
            for (i = 0; i < j; i++)
            {
                if (AllGrid[i].dx() == 0) AllGrid.Add(new GridElement(2 * base_x - AllGrid[i].X + 1, 2 * base_length - AllGrid[i].Length - AllGrid[i].Y, false, AllGrid[i].Length));
                else AllGrid.Add(new GridElement(2 * base_x - AllGrid[i].X + 2 - AllGrid[i].Length, 2 * base_length - AllGrid[i].Y - 1, true, AllGrid[i].Length));
            }

            if (ElementsCount % 2 != 0)
            {
                temp = SearchPosLeft(Left_Horizontal, Left_Vertical, base_x, base_length);
                if (temp != null) AllGrid.Add(temp);
                else
                {
                    temp = SearchPosRight(Right_Horizontal, Right_Vertical, base_x, base_length, right_max);
                    if (temp != null) AllGrid.Add(temp);
                    else return new List<GridElement>();
                }
            }
            return AllGrid;
        }
    }
}
